package com.zh.freechat.config;


import com.zh.freechat.config.filter.AuthTokenFilter;
import com.zh.freechat.domain.auth.JwtService;
import com.zh.freechat.domain.auth.LoginUser;
import com.zh.freechat.domain.auth.UserTokenWrap;
import com.zh.freechat.domain.user.ChatUserDetailsService;
import com.zh.freechat.domain.user.service.ChatUserService;
import com.zh.webcommon.common.auth.CollectPermitRequestURI;
import com.zh.webcommon.common.auth.LoginUserHolder;
import com.zh.webcommon.common.log.OperationLogRecorder;
import com.zh.webcommon.common.viewdata.ApiResult;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

import static com.zh.webcommon.CommonConst.GSON;

/**
 * Spring Security的主配置类
 * 采用不同的登录认证：
 * 后台用户表单登录，返回sessionid
 * web用户表单登录，返回token
 */
@Slf4j
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
    @Autowired
    private OperationLogRecorder operationLogRecorder;
    @Autowired
    private JwtService jwtService;

    /**
     * Spring Security核心配置
     * 过滤器链
     */
    @DependsOn("collectRequestURIScanner")
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //加载不需要权限认证的uri
        String[] permitURIs = CollectPermitRequestURI.getPermitRequestURI().toArray(String[]::new);
        MultiValueMap<HttpMethod, String> permitRequestURIsMap = CollectPermitRequestURI.getPermitRequestURIWithMethodScope();


        http
                //添加自定义的filter
                .addFilterBefore(new AuthTokenFilter(new String[]{"/auth/refresh","/auth/login"}), UsernamePasswordAuthenticationFilter.class)

                //访问认证配置
                .authorizeHttpRequests(expression -> {
                    expression.requestMatchers("/static/**", "/actuator/**").permitAll();

                    expression.requestMatchers(permitURIs).permitAll();

                    permitRequestURIsMap.forEach((httpMethod, uris) -> {
                        expression.requestMatchers(httpMethod, uris.toArray(String[]::new)).permitAll();
                    });

                    expression.anyRequest().authenticated();
                    // 开发方便：开发环境下不认证
//                    expression.anyRequest().permitAll();
                })
                //登录配置
                .formLogin(form -> form
                        .successHandler(authenticationSuccessHandler(operationLogRecorder))
                        .failureHandler(authenticationFailureHandler())
                )
                //登出配置
                .logout(logout ->
                        logout
                                .logoutSuccessHandler(logoutSuccessHandler())
                                .deleteCookies("JSESSIONID")
                )
                //屏蔽默认登陆页面
                .exceptionHandling(eh ->
                        eh
                                .accessDeniedHandler(accessDeniedHandler())
                                .authenticationEntryPoint(myAuthenticationEntryPoint())
                )

                //关闭csrf安全
                .csrf(csrf -> csrf.disable())
                .cors(Customizer.withDefaults());
        return http.build();
    }


//==========================================================
// 定义所需的Bean
//==========================================================

    @Bean
    public ChatUserDetailsService userDetailsService(ChatUserService userService) {
        return new ChatUserDetailsService(userService);
    }

    /**
     * 登录成功处理
     *
     * @return
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler(OperationLogRecorder operationLogRecorder) {
        /*
         * 获取请求来自客户端host
         */
        final Supplier<String> remoteHostFun = () -> {
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            return request.getRemoteHost();
        };

        return (req, resp, authentication) -> {
            log.info("登录成功处理Handler....");

            //将用户信息返回
            LoginUser userInfo = (LoginUser) authentication.getPrincipal();

            LoginUserHolder.putUser(userInfo.getUid(), userInfo.getUsername(), "");

            operationLogRecorder.loginLog(remoteHostFun.get(), userInfo.getName());

            String jwtToken = jwtService.createJwtToken(userInfo);
            String refreshToken = jwtService.createRefreshToken(userInfo.getUid(), userInfo.getUsername());

            UserTokenWrap userTokenWrap = new UserTokenWrap(jwtToken, refreshToken, userInfo);

            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write(GSON.toJson(ApiResult.success(userTokenWrap)));
        };
    }

    /**
     * 登录失败处理
     *
     * @return
     */
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (req, resp, ex) -> {
            log.warn("登录失败处理Handler.... " + ex.getMessage());
            resp.setStatus(HttpStatus.BAD_REQUEST.value());
            resp.setCharacterEncoding("UTF8");
            resp.setContentType(MediaType.APPLICATION_JSON_VALUE);
            resp.getWriter().write(GSON.toJson(ApiResult.fail(ex.getMessage())));
        };
    }

    /**
     * 登出成功处理
     */
    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return (req, resp, authentication) -> {
            log.info("退出登录成功处理Handler....");

            resp.setStatus(HttpStatus.OK.value());
        };
    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse resp, AccessDeniedException exception) throws IOException, ServletException {
                //将用户信息响应到前端
                resp.setContentType("application/json;charset=UTF-8");
                resp.setStatus(HttpStatus.FORBIDDEN.value());
                resp.getWriter().write(GSON.toJson(Map.of("message", exception.getMessage())));
            }
        };
    }

    @Bean
    public AuthenticationEntryPoint myAuthenticationEntryPoint() {
        return new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse resp, AuthenticationException exception) throws IOException, ServletException {
                //将用户信息响应到前端
                resp.setContentType("application/json;charset=UTF-8");
                resp.setStatus(HttpStatus.UNAUTHORIZED.value());
                resp.getWriter().write(GSON.toJson(Map.of("message", exception.getMessage())));
            }
        };
    }

    /**
     * 密码加密组件
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}